<template>
  <div class="zt">
    <div class="zt-tree">
      <input class="control" placeholder="通过名称搜索" v-model="lkey" type="text" @input="onLeftChange">
      <div class="ztree" id="zt-l-tree"></div>
    </div>
    <div class="zt-btns">
      <span class="zt-btn" :class="{'zt-btn-disable': !isCanAdd}" @click="handleAdd">&gt;</span>
      <span class="zt-btn" :class="{'zt-btn-disable': !isCanRemove}" @click="handleRemove">&lt;</span>
    </div>
    <div class="zt-tree">
      <input class="control" placeholder="通过名称搜索" v-model="rkey" type="text" @input="onRightChange">
      <div class="ztree" id="zt-r-tree"></div>
    </div>
  </div>
</template>

<script>
import "@ztree/ztree_v3/js/jquery-1.4.4.min.js";
import "@ztree/ztree_v3/js/jquery.ztree.all";
import "@ztree/ztree_v3/js/jquery.ztree.exhide.js";
import "@ztree/ztree_v3/css/zTreeStyle/zTreeStyle.css";
import "@ztree/ztree_v3/css/demo.css";

const $ = window.jQuery;

const clone = (obj) => {
  return JSON.parse(JSON.stringify(obj));
};

export default {
  props: {
    data: {
      type: Array,
      default: () => [],
    },
    defualtKeys: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      rData: [],
      lZtree: null,
      rZtree: null,
      isCanAdd: false,
      isCanRemove: false,
      lkey: '',
      rkey: ''
    };
  },

  mounted() {
    this.initTree();
  },

  methods: {
    /**
     * @description 初始化
     */
    initTree() {
      const setting = {
        data: {},
        treeId: "id",
        view: {
          showLine: false,
          showIcon: false
        },
        check: {
          enable: true,
        }
      };

      const lSetting = {
        ...setting,
        callback: {
          onCheck: this.onCheckLNode,
        },
      }

      const rSetting = {
        ...setting,
        callback: {
          onCheck: this.onCheckRNode,
        },
      }

      this.rData = clone(this.data);
      $.fn.zTree.init($("#zt-l-tree"), lSetting, this.data);
      $.fn.zTree.init($("#zt-r-tree"), rSetting, this.rData);
      this.lZtree = $.fn.zTree.getZTreeObj("zt-l-tree");
      this.rZtree = $.fn.zTree.getZTreeObj("zt-r-tree");

      const rZtree = this.rZtree;
      const rAllNodeList = rZtree.transformToArray(rZtree.getNodes());

      const initNodes = () => {
        rAllNodeList.forEach((node) => rZtree.hideNode(node));
        rAllNodeList.forEach((node) => {
          const isChecked = this.defualtKeys.some((key) => key === node.id);
          if (isChecked) {
            // 在右侧显示该节点和他的父节点
            rZtree.showNode(node);
            rZtree.showNode(node.getParentNode());
          }
        });
      };

      initNodes();
      this.updateNodeStatus();
      this.updateBtnStatus();
    },

    onCheckLNode(event, treeId, treeNode) {
      console.log(treeNode);
      this.updateBtnStatus();
    },

    onCheckRNode(event, treeId, treeNode) {
      console.log(treeNode);
      this.updateBtnStatus();
    },

    onRightChange() {
      const { lZtree, rZtree } = this;
      const flatter = lZtree.transformToArray;

      // 从左树中通过禁用状态，获取已经移到右边的点
      const nodes = flatter(lZtree.getNodes()).filter(el => el.chkDisabled);
      const getValue = () => {
        const values = [];
        const traverse = (list) => {
          list.forEach(n => {
            if (values.indexOf(n.id) === -1) {
              values.push(n.id);
              traverse(n.getPath())
            }
          });
        }
        traverse(nodes);
        return values;
      }
      const selectNodeId = getValue();
      const rNodes = flatter(rZtree.getNodes()).filter(el => { // 已经移到右侧的node
        return selectNodeId.indexOf(el.id) !== -1;
      });

      if (this.rkey) {
        // 先隐藏所有节点
        rZtree.hideNodes(rNodes);
        rNodes.forEach(n => {
          n.isHiddenByFilter = true;
        });
        // 展示匹配成功的节点，该节点的祖先节点也要展示，而且 open
        const traverse = (nodes => {
          nodes.forEach(n => {
            const reg = new RegExp(`${this.rkey}`)
            if (reg.test(n.name)){
              rZtree.showNodes(n.getPath());
              n.isHiddenByFilter = undefined;
              n.getPath().forEach(n => {
                n.isHiddenByFilter = undefined;
                rZtree.expandNode(n, true);
              })
            }
          });
        });
        traverse(rNodes);
      } else {
        rZtree.showNodes(rNodes);
        rNodes.forEach(n => {
          n.isHiddenByFilter = undefined;
        });
      }
      this.updateBtnStatus();
    },

    onLeftChange() {
      const { lZtree } = this;
      const flatter = lZtree.transformToArray;
      const nodes = flatter(lZtree.getNodes());
      if (this.lkey) {
        // 先隐藏所有节点
        lZtree.hideNodes(nodes);
        nodes.forEach(n => {
          n.isHiddenByFilter = true;
        });
        // 展示匹配成功的节点，该节点的祖先节点也要展示，而且 open
        const traverse = (nodes => {
          nodes.forEach(n => {
            const reg = new RegExp(`${this.lkey}`)
            if (reg.test(n.name)){
              n.isHiddenByFilter = undefined;
              lZtree.showNodes(n.getPath());
              n.getPath().forEach(n => {
                n.isHiddenByFilter = undefined;
                lZtree.expandNode(n, true);
              })
            }
          });
        });
        traverse(nodes);
      } else {
        lZtree.showNodes(nodes);
        nodes.forEach(n => {
          n.isHiddenByFilter = undefined;
        });
      }
      this.updateBtnStatus();
    },

    /**
     * @description 更新节点状态 & 更新按钮状态
     * 1. 某个叶子节点如果在右边，那么在左边它是选中状态 & 禁用状态
     * 2. 对于非叶子节点，如果他的子节点都是禁用状态，那么它也是禁用状态
     * 3. 对于非叶子节点，如果他的子节点存在一个选中，那么它也是选中状态
     */
    updateNodeStatus() {
      var lZtree = this.lZtree = $.fn.zTree.getZTreeObj("zt-l-tree");
      var rZtree = this.rZtree = $.fn.zTree.getZTreeObj("zt-r-tree");
      {
        // 左树：更新叶子节点状态
        // 左树: 更新非叶子节点状态
        // 后序遍历
        const rNodes = rZtree.transformToArray(rZtree.getNodes()).filter(el => !el.isHidden);
        const traverse = (nodes) => {
          nodes.forEach(n => {
            traverse(n.children || [])
            if (n.children) {
              const isChecked = n.children.filter(el => el.checked).length !== 0;
              const isDisabled = n.children.filter(el => !el.chkDisabled).length === 0;
              lZtree.checkNode(n, isChecked);
              lZtree.setChkDisabled(n, isDisabled);
            } else {
              const isChecked = rNodes.some(el => el.id === n.id);
              lZtree.checkNode(n, isChecked);
              lZtree.setChkDisabled(n, isChecked);
            }
          })
        }
        traverse(lZtree.getNodes())
      }
    },

    /**
     * 更新按钮样式
     */
    updateBtnStatus() {
      const { rZtree, lZtree } = this;
      const flatter = rZtree.transformToArray;
      {
        const lCheckedNodes = flatter(rZtree.getNodes()).filter(e => e.checked && !e.isHidden);
        this.isCanRemove = Boolean(lCheckedNodes.length);
      }
      {
        const selectedList = flatter(lZtree.getNodes()).filter(e => e.chkDisabled);
        const nodes = flatter(lZtree.getNodes())
          .filter(e => !e.isHidden && e.checked)
          .filter(n => {
            return !(selectedList.some(e => e.id === n.id))
          });
        this.isCanAdd = Boolean(nodes.length);
      }
    },

    /**
     * @description 通过ID查找结点
     */
    getNodeById(tree, id) {
      return tree.getNodeByParam('id', id);
    },

    /**
     * @description 左移
     * 将新增加的节点展示出来
     * 一个场景：
     * A 有子组件 A1 A2
     * 初始选中 A1
     * 将 A1 移除：此时在在右侧 A1 是选中状态，A是半选状态
     */
    handleAdd() {
      const { lZtree, rZtree } = this;
      // const nodes = lZtree.getCheckedNodes(); 不会获取禁用状态的节点
      const nodes = lZtree.transformToArray(lZtree.getNodes()).filter(el => el.checked);
      nodes.forEach(n => {
        rZtree.showNode(this.getNodeById(rZtree, n.id))
      })
      this.updateNodeStatus();
      this.updateBtnStatus();
      if (this.rkey) {
        this.onRightChange(this.rkey)
      }
    },

    /**
     * @description 右移
     * 将新选中的的节点: 取消选中 & 隐藏
     * 需要注意全选 和 半选
     */
    handleRemove() {
      const { rZtree, lZtree } = this;
      const nodes = rZtree.getCheckedNodes().filter(el => {
        return el.check_Child_State === -1 || el.check_Child_State === 2;
      });
      // 在左侧隐藏
      rZtree.checkAllNodes(false);
      rZtree.hideNodes(nodes);
      // 在右侧选中并且 移除 禁用状态
      nodes.forEach(n => {
        lZtree.setChkDisabled(this.getNodeById(lZtree, n.id), false);
        lZtree.checkNode(this.getNodeById(lZtree, n.id), true);
      });
      // 更新按钮状态
      this.updateBtnStatus();
    },
  },
};
</script>

<style lang="less" scoped>
.zt {
  display: flex;
}

.zt-tree {
  width: 300px;
  height: 300px;
  border: 1px solid #d6d8de;
}

.control {
  display: block;
  width: calc(100% - 32px);
  margin-left: 16px;
  margin-right: 16px;
  box-sizing:border-box;
  height: 32px;
  padding: 3px;
  margin-top: 10px;
}

.zt-btns {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 50px;
}

.zt-btn {
  display: inline-block;
  width: 30px;
  height: 30px;
  cursor: pointer;
}

.zt-btn-disable {
  opacity: .8;
  color: #ddd;
  cursor: not-allowed;
}
</style>

<style lang="less">
.ztree li {
  line-height: 28px;
  font-size: 14px;
}

.ztree * {
  font-family: "PingFang SC", "Microsoft YaHei", "微软雅黑", "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
}

// 箭头
.ztree li span.button.noline_open,
.ztree li span.button.noline_close {
  background-image: none;
  display: inline-block;
  content: " ";
  width: 0;
  height: 0;
  border: 4px solid transparent;
  border-left-color: #333;
  transition: all .2s ease-in-out;
  margin-left: 6px;
}
.ztree li span.button.noline_open {
  transform: rotate(90deg);
}

// a
.ztree li a {
  padding: 1px 3px 0 0;
  margin: 0;
  cursor: pointer;
  height: 17px;
  color: #333;
  background-color: transparent;
  text-decoration: none;
  vertical-align: top;
  display: inline-block;
}
.ztree li a:hover,
.ztree li a.curSelectedNode {
  height: 17px;
  opacity: 1;
  color: inherit;
  background-color: transparent;
  border: 0;
  text-decoration: none;
  padding-top: 1px;
}

// 复选按钮
.ztree li span.button.chk,
.ztree li span.button.chk:hover {
  position: relative;
  width: 14px;
  height: 14px;
  border: 1px solid #808695;
  background-image: none;
  border-radius: 2px;
  margin-left: 4px;
  margin-right: 4px;
  background-color: #fff;
  cursor: pointer;
  transition: border-color 0.2s ease-in-out, background-color 0.2s ease-in-out;
}

// 全选
.ztree li span.button.chk.checkbox_true_full_focus:after, 
.ztree li span.button.chk.checkbox_true_disable:after,
.ztree li span.button.chk.checkbox_true_full:after {
  display: table;
  box-sizing: border-box;
  content: " ";
  position: absolute;
  width: 10px;
  height: 16px;
  border: 3px solid #fc652f;
  top: -2px;
  left: 2px;
  border-top: 0;
  border-left: 0;
  transform: rotate(45deg) scale(0.5);
  transition: all .2s ease-in-out;
}

// 半选
.ztree li span.button.chk.checkbox_true_part_focus,
.ztree li span.button.chk.checkbox_true_part,
.ztree li span.button.chk.checkbox_false_part {
  background-position: 0 0;
  position: relative;
  &:after {
    display: inline-block;
    position: absolute;
    content: " ";
    left: 3px;
    top: 6px;
    width: 8px;
    height: 2px;
    background-color: #fc652f;
  }
}

// 不选
.ztree li span.button.chk.checkbox_false_full {
}

.ztree li span.button.chk.checkbox_true_disable {
  background: #f3f3f3;
  &:after {
    border-color: #424656;
  }
}
</style>